// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/ozone/platform/drm/host/drm_cursor.h"

#include "base/trace_event/trace_event.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/ozone/platform/drm/host/drm_window_host.h"
#include "ui/ozone/platform/drm/host/drm_window_host_manager.h"

#if defined(OS_CHROMEOS)
#include "ui/events/ozone/chromeos/cursor_controller.h"
#endif

namespace ui {

namespace {

    class NullProxy : public DrmCursorProxy {
    public:
        NullProxy() { }
        ~NullProxy() override { }

        void CursorSet(gfx::AcceleratedWidget window,
            const std::vector<SkBitmap>& bitmaps,
            const gfx::Point& point,
            int frame_delay_ms) override { }
        void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override { }

    private:
        DISALLOW_COPY_AND_ASSIGN(NullProxy);
    };

} // namespace

DrmCursor::DrmCursor(DrmWindowHostManager* window_manager)
    : window_(gfx::kNullAcceleratedWidget)
    , window_manager_(window_manager)
    , proxy_(new NullProxy())
{
}

DrmCursor::~DrmCursor() { }

void DrmCursor::SetDrmCursorProxy(DrmCursorProxy* proxy)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::SetDrmCursorProxy");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);
    proxy_.reset(proxy);
}

void DrmCursor::ResetDrmCursorProxy()
{
    TRACE_EVENT0("drmcursor", "DrmCursor::ResetDrmCursorProxy");
    DCHECK(thread_checker_.CalledOnValidThread());

    NullProxy* np = new NullProxy();
    base::AutoLock lock(lock_);
    proxy_.reset(np);
}

gfx::Point DrmCursor::GetBitmapLocationLocked()
{
    return gfx::ToFlooredPoint(location_) - bitmap_->hotspot().OffsetFromOrigin();
}

void DrmCursor::SetCursor(gfx::AcceleratedWidget window,
    PlatformCursor platform_cursor)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::SetCursor");
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK_NE(window, gfx::kNullAcceleratedWidget);

    scoped_refptr<BitmapCursorOzone> bitmap = BitmapCursorFactoryOzone::GetBitmapCursor(platform_cursor);

    base::AutoLock lock(lock_);

    if (window_ != window || bitmap_ == bitmap)
        return;

    bitmap_ = bitmap;

    SendCursorShowLocked();
}

void DrmCursor::OnWindowAdded(gfx::AcceleratedWidget window,
    const gfx::Rect& bounds_in_screen,
    const gfx::Rect& cursor_confined_bounds)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::OnWindowAdded");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);

    if (window_ == gfx::kNullAcceleratedWidget) {
        // First window added & cursor is not placed. Place it.
        window_ = window;
        display_bounds_in_screen_ = bounds_in_screen;
        confined_bounds_ = cursor_confined_bounds;
        SetCursorLocationLocked(gfx::PointF(cursor_confined_bounds.CenterPoint()));
    }
}

void DrmCursor::OnWindowRemoved(gfx::AcceleratedWidget window)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::OnWindowRemoved");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);

    if (window_ == window) {
        // Try to find a new location for the cursor.
        DrmWindowHost* dest_window = window_manager_->GetPrimaryWindow();

        if (dest_window) {
            window_ = dest_window->GetAcceleratedWidget();
            display_bounds_in_screen_ = dest_window->GetBounds();
            confined_bounds_ = dest_window->GetCursorConfinedBounds();
            SetCursorLocationLocked(gfx::PointF(confined_bounds_.CenterPoint()));
            SendCursorShowLocked();
        } else {
            window_ = gfx::kNullAcceleratedWidget;
            display_bounds_in_screen_ = gfx::Rect();
            confined_bounds_ = gfx::Rect();
            location_ = gfx::PointF();
        }
    }
}

void DrmCursor::CommitBoundsChange(
    gfx::AcceleratedWidget window,
    const gfx::Rect& new_display_bounds_in_screen,
    const gfx::Rect& new_confined_bounds)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::CommitBoundsChange");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);

    if (window_ == window) {
        display_bounds_in_screen_ = new_display_bounds_in_screen;
        confined_bounds_ = new_confined_bounds;
        SetCursorLocationLocked(location_);
        SendCursorShowLocked();
    }
}

void DrmCursor::MoveCursorTo(gfx::AcceleratedWidget window,
    const gfx::PointF& location)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursorTo (window)");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);
    gfx::AcceleratedWidget old_window = window_;

    if (window != old_window) {
        // When moving between displays, hide the cursor on the old display
        // prior to showing it on the new display.
        if (old_window != gfx::kNullAcceleratedWidget)
            SendCursorHideLocked();

        // TODO(rjk): pass this in?
        DrmWindowHost* drm_window_host = window_manager_->GetWindow(window);
        display_bounds_in_screen_ = drm_window_host->GetBounds();
        confined_bounds_ = drm_window_host->GetCursorConfinedBounds();
        window_ = window;
    }

    SetCursorLocationLocked(location);
    if (window != old_window)
        SendCursorShowLocked();
    else
        SendCursorMoveLocked();
}

void DrmCursor::MoveCursorTo(const gfx::PointF& screen_location)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursorTo");
    DCHECK(thread_checker_.CalledOnValidThread());
    base::AutoLock lock(lock_);

    // TODO(spang): Moving between windows doesn't work here, but
    // is not needed for current uses.
    SetCursorLocationLocked(screen_location - display_bounds_in_screen_.OffsetFromOrigin());

    SendCursorMoveLocked();
}

void DrmCursor::MoveCursor(const gfx::Vector2dF& delta)
{
    TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursor");
    base::AutoLock lock(lock_);

    if (window_ == gfx::kNullAcceleratedWidget)
        return;

    gfx::Point location;
#if defined(OS_CHROMEOS)
    gfx::Vector2dF transformed_delta = delta;
    ui::CursorController::GetInstance()->ApplyCursorConfigForWindow(
        window_, &transformed_delta);
    SetCursorLocationLocked(location_ + transformed_delta);
#else
    SetCursorLocationLocked(location_ + delta);
#endif
    SendCursorMoveLocked();
}

bool DrmCursor::IsCursorVisible()
{
    base::AutoLock lock(lock_);
    return bitmap_;
}

gfx::PointF DrmCursor::GetLocation()
{
    base::AutoLock lock(lock_);
    return location_ + display_bounds_in_screen_.OffsetFromOrigin();
}

gfx::Rect DrmCursor::GetCursorConfinedBounds()
{
    base::AutoLock lock(lock_);
    return confined_bounds_ + display_bounds_in_screen_.OffsetFromOrigin();
}

void DrmCursor::SetCursorLocationLocked(const gfx::PointF& location)
{
    gfx::PointF clamped_location = location;
    clamped_location.SetToMax(gfx::PointF(confined_bounds_.origin()));
    // Right and bottom edges are exclusive.
    clamped_location.SetToMin(
        gfx::PointF(confined_bounds_.right() - 1, confined_bounds_.bottom() - 1));

    location_ = clamped_location;
}

void DrmCursor::SendCursorShowLocked()
{
    if (!bitmap_) {
        SendCursorHideLocked();
        return;
    }
    CursorSetLockTested(window_, bitmap_->bitmaps(), GetBitmapLocationLocked(),
        bitmap_->frame_delay_ms());
}

void DrmCursor::SendCursorHideLocked()
{
    CursorSetLockTested(window_, std::vector<SkBitmap>(), gfx::Point(), 0);
}

void DrmCursor::SendCursorMoveLocked()
{
    if (!bitmap_)
        return;
    MoveLockTested(window_, GetBitmapLocationLocked());
}

// Lock-testing helpers.
void DrmCursor::CursorSetLockTested(gfx::AcceleratedWidget window,
    const std::vector<SkBitmap>& bitmaps,
    const gfx::Point& point,
    int frame_delay_ms)
{
    lock_.AssertAcquired();
    proxy_->CursorSet(window, bitmaps, point, frame_delay_ms);
}

void DrmCursor::MoveLockTested(gfx::AcceleratedWidget window,
    const gfx::Point& point)
{
    lock_.AssertAcquired();
    proxy_->Move(window, point);
}

} // namespace ui
